#if defined(UNICODE) && !defined(_UNICODE)
    #define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
    #define UNICODE
#endif

#include <tchar.h>
#include <windows.h>
#include <Winuser.h>
#include <Winbase.h>
#include <Richedit.h>
#include <windef.h>

LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK LL_KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);

/*  Make the class name into a global variable  */
TCHAR szClassName[ ] = _T("BabyTyperWindowsApp");

int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nCmdShow)
{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_WINLOGO);
    wincl.hIconSm = LoadIcon (NULL, IDI_WINLOGO);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    hwnd = CreateWindowEx(WS_EX_APPWINDOW, szClassName, _T("Baby Typer"), WS_OVERLAPPED|WS_POPUP, 0, 0, (int)GetSystemMetrics(SM_CXSCREEN), (int)GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hThisInstance, NULL);

    ShowWindow (hwnd, nCmdShow);

    HHOOK hook1 = SetWindowsHookEx(WH_KEYBOARD_LL, LL_KeyboardProc, hThisInstance, 0);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    UnhookWindowsHookEx(hook1);

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}

BOOL altPressed = 0;

LRESULT CALLBACK LL_KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode < 0)
        return CallNextHookEx(0, nCode, wParam, lParam);

    PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;

    if ((p->vkCode == VK_F4) && (p->flags & LLKHF_ALTDOWN))
        PostQuitMessage(0);

//    if ((p->vkCode == VK_CONTROL) || (p->vkCode == VK_SHIFT) || (p->vkCode & VK_MENU))
//        return CallNextHookEx(0, nCode, wParam, lParam);

    if (p->flags & LLKHF_ALTDOWN)
        return 1;

//    if (p->vkCode == VK_CAPITAL)
//        return CallNextHookEx(0, nCode, wParam, lParam);

    if (
        (p->vkCode == VK_SPACE) ||
        (p->vkCode == VK_RETURN) ||
        (p->vkCode == VK_BACK) ||
        ((p->vkCode >= 0x30) && (p->vkCode <= 0x39)) ||
        ((p->vkCode >= 0x41) && (p->vkCode <= 0x5A)) ||
        ((p->vkCode >= VK_NUMPAD0) && (p->vkCode <= VK_DIVIDE)) ||
        ((p->vkCode >= VK_OEM_1) && (p->vkCode <= VK_OEM_3)) ||
        ((p->vkCode >= VK_OEM_4) && (p->vkCode <= VK_OEM_8))
    )
        return CallNextHookEx(0, nCode, wParam, lParam);

    return 1;
}

WNDPROC DefEditProc;

LRESULT EditKeyProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  if (uMsg == WM_KEYDOWN) {
    if (wParam == VK_BACK) {
        CallWindowProc(DefEditProc, hwnd, uMsg, wParam, lParam);
//        CallWindowProc(DefEditProc, hwnd, WM_KEYUP, wParam, lParam);
        return CallWindowProc(DefEditProc, hwnd, uMsg, wParam, lParam);
    }
  }
  if ((uMsg == WM_CHAR) && (wParam >= 0x0A)) {
    CallWindowProc(DefEditProc, hwnd, uMsg, (WPARAM)CharUpper((LPSTR)wParam), lParam);
    return CallWindowProc(DefEditProc, hwnd, uMsg, (WPARAM)' ', lParam);
  }
  switch (uMsg) {
    case  WM_LBUTTONDBLCLK:
    case  WM_LBUTTONDOWN:
    case  WM_LBUTTONUP:
    case  WM_MBUTTONDBLCLK:
    case  WM_MBUTTONDOWN:
    case  WM_MBUTTONUP:
    case  WM_MOUSEACTIVATE:
    case  WM_NCHITTEST:
    case  WM_NCLBUTTONDBLCLK:
    case  WM_NCLBUTTONDOWN:
    case  WM_NCLBUTTONUP:
    case  WM_NCMBUTTONDBLCLK:
    case  WM_NCMBUTTONDOWN:
    case  WM_NCMBUTTONUP:
    case  WM_NCRBUTTONDBLCLK:
    case  WM_NCRBUTTONDOWN:
    case  WM_NCRBUTTONUP:
    case  WM_RBUTTONDBLCLK:
    case  WM_RBUTTONDOWN:
    case  WM_RBUTTONUP:
    return 0;
  }
  return CallWindowProc(DefEditProc, hwnd, uMsg, wParam, lParam);
}

#define ID_EDITCHILD 100

LRESULT CALLBACK WindowProcedure(HWND hwnd,      // window handle
                             UINT message,   // type of message
                             WPARAM wParam,  // additional information
                             LPARAM lParam)  // additional information
{
    static HWND hwndEdit;

    switch (message)
    {
        case WM_CREATE: {
            LoadLibrary(_T("riched20.dll"));
            hwndEdit = CreateWindowEx(
                                0, RICHEDIT_CLASS,   // predefined class _T("EDIT"), //
                                NULL,         // no window title
                                ES_MULTILINE | WS_VISIBLE | WS_CHILD | WS_BORDER | WS_TABSTOP | WS_VSCROLL,
                                0, 0, 0, 0,   // set size in WM_SIZE message
                                hwnd,         // parent window
                                (HMENU) ID_EDITCHILD,   // edit control ID
                                (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
                                NULL);        // pointer not needed

            HFONT hFont = CreateFont(128, 64, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
                                OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY,
                                DEFAULT_PITCH | FF_DONTCARE, _T("Tahoma"));

            SendMessage(hwndEdit, WM_SETFONT, (WPARAM) hFont, (LPARAM) TRUE);

            SendMessage(hwndEdit, WM_CHAR, (WPARAM) ' ', 0);

            DefEditProc = (WNDPROC)SetWindowLong(hwndEdit, GWL_WNDPROC, (long)EditKeyProc);

            return 0;
        }

        case WM_SETFOCUS:
            SetFocus(hwndEdit);
            return 0;

        case WM_SIZE:
            // Make the edit control the size of the window's client area.

            MoveWindow(hwndEdit,
                       0, 0,                  // starting x- and y-coordinates
                       LOWORD(lParam),        // width of client area
                       HIWORD(lParam),        // height of client area
                       TRUE);                 // repaint window
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}
